import os
import json
import asyncio
import requests
from typing import Dict, List, Any
from loguru import logger
import re

# from frontend.app import question

class OllamaClient:
    """
    Ollama API客户端，负责与本地部署的DeepSeek-R1:70b模型交互
    """
    def __init__(self, base_url: str, model: str):
        self.base_url = base_url
        self.model = model
        self.generate_endpoint = f"{base_url}/api/generate"
        self.timeout = 30  # 默认超时时间（秒）
        self.detailed_schema = None
        self.meta_graph = """
        {"nodes":[{"id":"-12","labels":["DiseaseName"],"properties":{"name":"DiseaseName","indexes":["name"],"constraints":[]}},{"id":"-8","labels":["BridgeMember"],"properties":{"name":"BridgeMember","indexes":["name"],"constraints":[]}},{"id":"-9","labels":["DiseaseClass"],"properties":{"name":"DiseaseClass","indexes":["name"],"constraints":[]}},{"id":"-10","labels":["QualitativeDes"],"properties":{"name":"QualitativeDes","indexes":["name"],"constraints":[]}},{"id":"-11","labels":["QuantitativeDes"],"properties":{"name":"QuantitativeDes","indexes":["name"],"constraints":[]}},{"id":"-7","labels":["BridgeType"],"properties":{"name":"BridgeType","indexes":["name"],"constraints":[]}}],"relationships":[{"id":"-10","type":"has_member","startNode":"-7","endNode":"-8","properties":{"name":"has_member"}},{"id":"-7","type":"has_class_of","startNode":"-12","endNode":"-9","properties":{"name":"has_class_of"}},{"id":"-8","type":"has_qualitative_description","startNode":"-9","endNode":"-10","properties":{"name":"has_qualitative_description"}},{"id":"-9","type":"has_quantitative_description","startNode":"-9","endNode":"-11","properties":{"name":"has_quantitative_description"}},{"id":"-6","type":"may_occur","startNode":"-8","endNode":"-12","properties":{"name":"may_occur"}}]}
        """
        # from langchain_community.graphs import Neo4jGraph
        # enhanced_graph = Neo4jGraph(
        #     # url="bolt://localhost:7687",
        #     url = "bolt://10.14.120.13:7687",
        #     username="neo4j",
        #     password="11223344",
        #     database="neo4j",
        #     timeout=20,  # 设置连接超时时间为 60 秒
        #     enhanced_schema=True
        # )
    
    async def test_connection(self) -> bool:
        """
        测试与Ollama服务的连接
        """
        try:
            response = requests.get(f"{self.base_url}/api/tags", timeout=5)
            response.raise_for_status()
            return True
        except Exception as e:
            logger.error(f"Ollama连接测试失败: {str(e)}")
            raise e
    
    async def generate(self, prompt: str, system_prompt: str = None, temperature: float = 0.7,stream_flag = False) -> str:
        """
        生成文本响应
        
        Args:
            prompt: 用户输入的提示词
            system_prompt: 系统提示词（可选）
            temperature: 温度参数，控制随机性
            
        Returns:
            模型生成的文本响应
        """
        payload = {
            "model": self.model,
            "prompt": f"{prompt}\n/nothink",  # 换行后再加
            "temperature": temperature,
            "stream": stream_flag
        }
        
        if system_prompt:
            payload["system"] = system_prompt
        
        try:
            response = requests.post(
                self.generate_endpoint,
                json=payload,
                timeout=self.timeout
            )
            response.raise_for_status()
            result = response.json()
            return re.sub(r'<think>[\s\S]*?</think>', '',result.get("response", "")).strip()
        except requests.exceptions.Timeout:
            logger.error(f"Ollama API请求超时: {prompt[:100]}...")
            raise Exception("模型响应超时，请稍后重试或简化您的问题")
        except Exception as e:
            logger.error(f"Ollama API请求失败: {str(e)}\n提示词: {prompt[:100]}...")
            raise e
    
    async def parse_intent(self, question: str) -> Dict[str, Any]:
        """
        解析用户问题的意图
        
        Args:
            question: 用户问题
            
        Returns:
            包含意图信息的字典
        """
        system_prompt = """
        你是一个专门针对 **桥梁病害知识图谱** 的问题意图解析器。你的任务是分析用户关于桥梁结构、病害及其关系的提问，提取关键实体、关系和查询意图。
        请根据用户的提问，严格按照以下JSON格式返回分析结果：

        {
        "query_type": "查询类型，必须是以下之一：\n   - 'fact'：查询某个实体（如特定病害、构件）的属性或直接关联信息（如分类、描述）。\n   - 'relationship'：查询两个或多个指定实体之间是否存在特定关系，或查询它们之间的关系类型。\n   - 'list'：获取满足特定条件（如属于某类型、具有某种关系）的实体列表。\n   - 'connection'：查询从一个实体出发，通过特定关系能到达哪些实体（或反之）。\n   - 'existence'：判断某个实体或关系是否存在。\n   （注意：'path' 和 'inference' 类型在此Schema下可能不太常用，优先使用以上类型）",
        "entities": [
            {
            "name": "实体名称，例如 '钢箱梁', '桥梁墩台腐蚀'",
            "type": "实体类型，必须是以下之一：'DiseaseName', 'StructureType', 'BridgeMember', 'DiseaseClass', 'QualitativeDes', 'QuantitativeDes', 'BridgeType'。",
            "role": "实体在查询中的角色（可选，如 'subject', 'object'）"
            }
        ],
        "relationships": [
            {
            "type": "关系类型，必须是以下之一：'has_member', 'has_class_of', 'has_qualitative_description', 'has_quantitative_description', 'may_occur'。",
            "source": "关系源实体的名称（如果能从问题中确定）",
            "target": "关系目标实体的名称（如果能从问题中确定）"
            }
        ],
        "constraints": [
            "查询的约束条件，例如：{'property': 'name', 'value': '钢箱梁'}"
        ],
        "target_info": "用户明确想要查询的信息类型或目标（可选，例如：'DiseaseClass', 'QualitativeDes', 'BridgeMember'列表）",
        "context": "对问题的简要概括或补充说明（可选）"
        }

        **处理要求:**
        1.  仔细分析用户问题，识别出与知识图谱 Schema 对应的实体名称和类型。
        2.  识别用户想查询的关系类型，必须是 Schema 中存在的类型。
        3.  根据问题的核心意图，选择最合适的 `query_type`。
        4.  `entities` 列表应包含问题中明确提及或隐含的关键实体。
        5.  `relationships` 列表应包含问题中明确提及或隐含的关系。如果问题是查询关系的目标（例如，“A有什么B？”），则目标实体名称可能为 `null` 或省略。
        6.  **只返回JSON格式的结果，不要包含任何解释性文字、代码标记（如 ```json ... ```）或其他额外文本。**
        7.  确保JSON格式严格有效，可以被程序直接解析。

        **Schema 概览供参考:**
        Node properties:
        - **StructureType**
        - `结构类型`: STRING Available options: ['预应力空心板']
        - `StructureTypeId`: INTEGER Min: 30101, Max: 30101
        - **BridgeType**
        - `name`: STRING Available options: ['悬索桥']
        - **BridgeMember**
        - `name`: STRING Available options: ['主缆', '索夹', '吊索', '预应力混凝土加劲梁', '钢桁架加劲梁', '钢箱加劲梁', '索塔', '索鞍', '锚碇', '锚杆']
        - **DiseaseClass**
        - `name`: STRING Example: "1级主缆防护损坏"
        - **QualitativeDes**
        - `name`: STRING Example: "完好"
        - **QuantitativeDes**
        - `name`: STRING Example: "面漆变色起皮面积≤3%,或防护破损面积≤1%"
        - **DiseaseName**
        - `name`: STRING Example: "主缆防护损坏"

        Relationship properties:
        The relationships:
        (:BridgeType)-[:has_member]->(:BridgeMember)
        (:BridgeMember)-[:may_occur]->(:DiseaseName)
        (:DiseaseClass)-[:has_qualitative_description]->(:QualitativeDes)
        (:DiseaseClass)-[:has_quantitative_description]->(:QuantitativeDes)
        (:DiseaseName)-[:has_class_of]->(:DiseaseClass)

        neo4j中图谱的详细的meta_graph，或者说Schema：**
        "meta_graph": {"nodes":[{"id":"-12","labels":["DiseaseName"],"properties":{"name":"DiseaseName","indexes":["name"],"constraints":[]}},{"id":"-8","labels":["BridgeMember"],"properties":{"name":"BridgeMember","indexes":["name"],"constraints":[]}},{"id":"-9","labels":["DiseaseClass"],"properties":{"name":"DiseaseClass","indexes":["name"],"constraints":[]}},{"id":"-10","labels":["QualitativeDes"],"properties":{"name":"QualitativeDes","indexes":["name"],"constraints":[]}},{"id":"-11","labels":["QuantitativeDes"],"properties":{"name":"QuantitativeDes","indexes":["name"],"constraints":[]}},{"id":"-7","labels":["BridgeType"],"properties":{"name":"BridgeType","indexes":["name"],"constraints":[]}}],"relationships":[{"id":"-10","type":"has_member","startNode":"-7","endNode":"-8","properties":{"name":"has_member"}},{"id":"-7","type":"has_class_of","startNode":"-12","endNode":"-9","properties":{"name":"has_class_of"}},{"id":"-8","type":"has_qualitative_description","startNode":"-9","endNode":"-10","properties":{"name":"has_qualitative_description"}},{"id":"-9","type":"has_quantitative_description","startNode":"-9","endNode":"-11","properties":{"name":"has_quantitative_description"}},{"id":"-6","type":"may_occur","startNode":"-8","endNode":"-12","properties":{"name":"may_occur"}}]}

        
        **示例问题:** "查询'钢箱梁'这种桥梁构件可能会发生哪些疾病？"
        **期望的JSON输出（仅作为例子，实际输出时不包含此示例）：**
        ```json
        {
        "query_type": "connection",
        "entities": [
            {
            "name": "钢箱梁",
            "type": "BridgeMember",
            "role": "subject"
            }
        ],
        "relationships": [
            {
            "type": "may_occur",
            "source": "钢箱梁",
            "target": null
            }
        ],
        "constraints": [],
        "target_info": "DiseaseName",
        "context": "User wants to find diseases associated with the '钢箱梁' bridge member via the 'may_occur' relationship."
        }
        """
        
        prompt = f"分析以下问题的意图，提取关键实体和查询类型:\n{question}"
        
        try:
            response = await self.generate(prompt, system_prompt, temperature=0.1)
            # 提取JSON部分
            json_str = response.strip()
            if json_str.startswith("```json"):
                json_str = json_str.replace("```json", "").replace("```", "")
            elif json_str.startswith("{"): 
                # 已经是JSON格式，不需要额外处理
                pass
            else:
                # 尝试从文本中提取JSON部分
                import re
                json_match = re.search(r'\{.*\}', json_str, re.DOTALL)
                if json_match:
                    json_str = json_match.group(0)
                else:
                    raise json.JSONDecodeError("无法从响应中提取JSON", json_str, 0)
            
            intent_data = json.loads(json_str)
            
            # 确保返回的数据包含所有必要字段
            if "query_type" not in intent_data:
                intent_data["query_type"] = "fact"
            if "entities" not in intent_data:
                intent_data["entities"] = []
            if "relationships" not in intent_data:
                intent_data["relationships"] = []
            if "context" not in intent_data:
                intent_data["context"] = question
            if "constraints" not in intent_data:
                intent_data["constraints"] = []
                
            return intent_data
        except json.JSONDecodeError as e:
            logger.error(f"无法解析意图响应为JSON: {e}\n响应内容: {response}")
            # 返回默认意图
            return {
                "query_type": "fact",
                "entities": [],
                "relationships": [],
                "constraints": [],
                "context": question
            }
        except Exception as e:
            logger.error(f"解析意图时发生错误: {str(e)}")
            # 返回默认意图
            return {
                "query_type": "fact",
                "entities": [],
                "relationships": [],
                "constraints": [],
                "context": question
            }
    
    async def generate_cypher(self, intent: Dict[str, Any], question) -> str:
        """
        根据用户问题和解析的意图生成Cypher查询语句
        
        Args:
            intent: 意图解析结果
            
        Returns:
            Cypher查询语句
        """
        system_prompt = """
        你是一个专门为 **桥梁病害知识图谱** 生成 Neo4j Cypher 查询语句的专家。你的任务是根据提供的 JSON 格式的用户查询意图，生成精确且高效的 Cypher 查询语句。

        **生成 Cypher 查询时请严格遵循以下规则:**

        1.  **使用 Schema:** 必须使用知识图谱中定义的节点标签（`DiseaseName`, `StructureType`, `BridgeMember`, `DiseaseClass`, `QualitativeDes`, `QuantitativeDes`, `BridgeType`）和关系类型（`has_member`, `has_class_of`, `has_qualitative_description`, `has_quantitative_description`, `may_occur`）。
        neo4j中图谱的详细的meta_graph，或者说Schema：**
        "meta_graph": {"nodes":[{"id":"-12","labels":["DiseaseName"],"properties":{"name":"DiseaseName","indexes":["name"],"constraints":[]}},{"id":"-8","labels":["BridgeMember"],"properties":{"name":"BridgeMember","indexes":["name"],"constraints":[]}},{"id":"-9","labels":["DiseaseClass"],"properties":{"name":"DiseaseClass","indexes":["name"],"constraints":[]}},{"id":"-10","labels":["QualitativeDes"],"properties":{"name":"QualitativeDes","indexes":["name"],"constraints":[]}},{"id":"-11","labels":["QuantitativeDes"],"properties":{"name":"QuantitativeDes","indexes":["name"],"constraints":[]}},{"id":"-7","labels":["BridgeType"],"properties":{"name":"BridgeType","indexes":["name"],"constraints":[]}}],"relationships":[{"id":"-10","type":"has_member","startNode":"-7","endNode":"-8","properties":{"name":"has_member"}},{"id":"-7","type":"has_class_of","startNode":"-12","endNode":"-9","properties":{"name":"has_class_of"}},{"id":"-8","type":"has_qualitative_description","startNode":"-9","endNode":"-10","properties":{"name":"has_qualitative_description"}},{"id":"-9","type":"has_quantitative_description","startNode":"-9","endNode":"-11","properties":{"name":"has_quantitative_description"}},{"id":"-6","type":"may_occur","startNode":"-8","endNode":"-12","properties":{"name":"may_occur"}}]}
        2.  **根据 `query_type` 选择模式:**
            *   `fact`: 查询特定实体的属性或直接关联信息。通常使用 `MATCH (n:EntityType {prop: value}) RETURN n` 或 `MATCH (n:EntityType {prop: value})-[r]-(m) RETURN n, r, m`。
            *   `relationship`: 查询两个或多个 *指定* 实体间的关系，（生成查询时，关系部分的描述不要使用"->"，请使用"-"）。通常使用 `MATCH (a:TypeA {propA: valA})-[r:RelType]-(b:TypeB {propB: valB}) RETURN a, r, b`。
            *   `list`: 获取满足条件的实体列表。通常使用 `MATCH (n:EntityType) WHERE condition RETURN n`。重点是返回符合条件的节点 `n`。
            *   `connection`: 查询从一个实体出发，通过特定关系连接到的实体（生成查询时，关系部分的描述不要使用"->"，请使用"-"）。通常使用 `MATCH (a:TypeA {propA: valA})-[r:RelType]-(b:TargetType) RETURN a, r, b` (如果 `TargetType` 在 `target_info` 中指定) 或 `MATCH (a:TypeA {propA: valA})-[r:RelType]-(b) RETURN a, r, b`。
            *   `existence`: 判断实体或关系是否存在。通常使用 `MATCH path = (a:TypeA {...})-[r:RelType]-(b:TypeB {...}) RETURN path LIMIT 1` 或 `MATCH (n:Type {prop:val}) RETURN n LIMIT 1`，查询调用方检查是否有返回结果。
        3.  **利用输入信息:**
            *   使用 `entities` 列表中的 `name` 和 `type` 来定位节点。优先使用 `name` 属性进行匹配（因为很多 `name` 属性有索引）。对于 `QualitativeDes` 和 `QuantitativeDes`，可能需要匹配 `description` 属性。
            *   使用 `relationships` 列表中的 `type` 来指定关系类型，并根据 `source` 和 `target` 确定方向和端点。
            *   考虑 `constraints` 来添加 `WHERE` 子句中的过滤条件。
            *   `target_info` 可以用来指定 `RETURN` 子句中重点关注的节点类型或信息。
        4.  **使用模糊匹配:** 对于实体名称匹配，建议使用 `CONTAINS` 或 `STARTS WITH` 进行部分匹配，并结合 `toLower()` 实现不区分大小写，以提高查询成功率。例如：`WHERE toLower(n.name) CONTAINS toLower('钢箱')`。**注意：** 如果属性有索引 (`name`, `description`)，`STARTS WITH` 或精确匹配 (`=`) 通常比 `CONTAINS` 性能更好。优先考虑精确匹配或 `STARTS WITH`。
        5.  **限制结果数量:** 在查询末尾使用 `LIMIT 50`（或根据需要调整，例如 `LIMIT 1` 用于存在性检查）来避免返回过大的结果集，保护数据库性能。
        6.  **返回结构:** 默认情况下，`RETURN` 子句应包含能表示查询结果路径或关键信息的节点和关系（例如 `RETURN a, r, b` 或 `RETURN n`）。根据 `query_type` 和 `target_info` 适当调整。
        7.  **仅输出 Cypher:** **绝对只返回 Cypher 查询语句字符串，不包含任何解释性文字、代码标记（如 ```cypher ... ```）、确认信息或任何其他字符。**

        **示例 (根据上面的意图 JSON):**

        ```cypher
        MATCH (a:BridgeMember)-[r:may_occur]-(b:DiseaseName) WHERE toLower(a.name) CONTAINS toLower('钢箱梁') RETURN a, r, b LIMIT 50
        ```

        **请根据接下来提供的意图 JSON 生成相应的 Cypher 查询语句。**
        """
        
        # 根据意图类型构建更具体的提示
        query_type = intent.get("query_type", "fact")
        entities = intent.get("entities", [])
        relationships = intent.get("relationships", [])
        constraints = intent.get("constraints", [])
        
        # 构建实体描述
        entity_descriptions = []
        for entity in entities:
            if isinstance(entity, dict) and "name" in entity:
                entity_descriptions.append(f"实体名称: {entity['name']}")
                if "type" in entity and entity["type"]:
                    entity_descriptions.append(f"实体类型: {entity['type']}")
            elif isinstance(entity, str):
                entity_descriptions.append(f"实体名称: {entity}")
        
        entity_text = "\n".join(entity_descriptions) if entity_descriptions else "未提供实体信息"
        
        # 构建关系描述
        relationship_descriptions = []
        for rel in relationships:
            if isinstance(rel, dict) and "type" in rel:
                rel_desc = f"关系类型: {rel['type']}"
                if "source" in rel and rel["source"]:
                    rel_desc += f", 源实体: {rel['source']}"
                if "target" in rel and rel["target"]:
                    rel_desc += f", 目标实体: {rel['target']}"
                relationship_descriptions.append(rel_desc)
            elif isinstance(rel, str):
                relationship_descriptions.append(f"关系类型: {rel}")
        
        relationship_text = "\n".join(relationship_descriptions) if relationship_descriptions else "未提供关系信息"
        
        # 构建约束描述
        constraint_text = "\n".join([str(c) for c in constraints]) if constraints else "无特殊约束"
        
        # 构建提示词
        prompt = f"""根据以下意图信息生成Neo4j Cypher查询语句：
        原始问题：{question}

        查询类型: {query_type}
        
        实体信息:
        {entity_text}
        
        关系信息:
        {relationship_text}
        
        约束条件:
        {constraint_text}
        
        原始问题上下文:
        {intent.get('context', '')}
        
        请生成一个精确的Cypher查询语句，确保查询结果包含足够的信息以回答问题。
        """
        
        try:
            response = await self.generate(prompt, system_prompt, temperature=0.1)
       
            cypher = self.extract_cypher_query(response)
            
            # 确保查询有LIMIT子句
            cypher = cypher.strip()
            if "LIMIT" not in cypher.upper():
                cypher += " LIMIT 50"
                
            return cypher
        except Exception as e:
            logger.error(f"生成Cypher查询失败: {str(e)}")
            # 返回一个基本的查询作为后备
            if entities and isinstance(entities[0], dict) and "name" in entities[0]:
                entity_name = entities[0]["name"]
                return f"MATCH (n) WHERE toLower(n.name) CONTAINS toLower('{entity_name}') RETURN n LIMIT 20"
            elif entities and isinstance(entities[0], str):
                return f"MATCH (n) WHERE toLower(n.name) CONTAINS toLower('{entities[0]}') RETURN n LIMIT 20"
            else:
                return "MATCH (n) RETURN n LIMIT 10"

    async def cypher_check(self, cypher_init: str,question: str) -> str:
        """
        检查生成的Cypher查询是否有效

        Args:
            cypher: 生成的Cypher查询语句
            question: 用户的原始问题

        Returns:
            检查后最终确定的cypher的query
              
        """
        system_prompt = """
        你是一位经验丰富的 **知识图谱和 Neo4j Cypher 专家**，并且擅长理解用户意图。你的任务是接收一个初步生成的 Cypher 查询语句、用户的原始自然语言问题、以及该知识图谱的 Schema 结构信息，然后对这个 Cypher 查询进行严格的审核和必要的修正。

        **你的目标是：** 输出一个最终的、**语义上正确** 且 **符合 Schema 结构** 的 Cypher 查询语句，该语句最能准确地回答用户的原始问题。

        **输入信息将包括：**

        1.  **原始用户问题 (Original User Question):** 用户用自然语言提出的查询。
        2.  **知识图谱 Schema (Graph Schema):**
            *   **节点标签:** `DiseaseName`, `StructureType`, `BridgeMember`, `DiseaseClass`, `QualitativeDes`, `QuantitativeDes`, `BridgeType`
            *   **关系类型:** `has_member`, `has_class_of`, `has_qualitative_description`, `has_quantitative_description`, `may_occur`
            *   **已知连接模式:**
                *   `(BridgeType)-[:has_member]->(BridgeMember)`
                *   `(DiseaseName)-[:has_class_of]->(DiseaseClass)`
                *   `(DiseaseClass)-[:has_qualitative_description]->(QualitativeDes)`
                *   `(DiseaseClass)-[:has_quantitative_description]->(QuantitativeDes)`
                *   `(BridgeMember)-[:may_occur]->(DiseaseName)`
            *   **常用属性 (带索引):** `name` (在 DiseaseName, BridgeMember, DiseaseClass, BridgeType 上), `description` (在 QualitativeDes, QuantitativeDes 上)
            neo4j中图谱的详细的meta_graph，或者说Schema：**
            "meta_graph": {"nodes":[{"id":"-12","labels":["DiseaseName"],"properties":{"name":"DiseaseName","indexes":["name"],"constraints":[]}},{"id":"-8","labels":["BridgeMember"],"properties":{"name":"BridgeMember","indexes":["name"],"constraints":[]}},{"id":"-9","labels":["DiseaseClass"],"properties":{"name":"DiseaseClass","indexes":["name"],"constraints":[]}},{"id":"-10","labels":["QualitativeDes"],"properties":{"name":"QualitativeDes","indexes":["name"],"constraints":[]}},{"id":"-11","labels":["QuantitativeDes"],"properties":{"name":"QuantitativeDes","indexes":["name"],"constraints":[]}},{"id":"-7","labels":["BridgeType"],"properties":{"name":"BridgeType","indexes":["name"],"constraints":[]}}],"relationships":[{"id":"-10","type":"has_member","startNode":"-7","endNode":"-8","properties":{"name":"has_member"}},{"id":"-7","type":"has_class_of","startNode":"-12","endNode":"-9","properties":{"name":"has_class_of"}},{"id":"-8","type":"has_qualitative_description","startNode":"-9","endNode":"-10","properties":{"name":"has_qualitative_description"}},{"id":"-9","type":"has_quantitative_description","startNode":"-9","endNode":"-11","properties":{"name":"has_quantitative_description"}},{"id":"-6","type":"may_occur","startNode":"-8","endNode":"-12","properties":{"name":"may_occur"}}]}

        3.  **初步生成的 Cypher 查询 (Generated Cypher Query):** 需要被审核和修正的查询语句。

        **审核和修正规则：**

        1.  **Schema 一致性检查 (关键):**
            *   检查查询中使用的节点标签是否在 Schema 中存在。
            *   检查查询中使用的关系类型是否在 Schema 中存在。
            *   **最重要:** 检查 `MATCH` 子句中的连接模式是否符合 Schema 中定义的已知连接模式。例如，`may_occur` 关系是否确实连接了 `BridgeMember` 和 `DiseaseName`？查询是否试图连接 Schema 中不存在直接关系的节点类型？
            *   检查使用的属性名称是否在对应节点/关系类型中常用（尤其是 `name` 和 `description`）。
            *   查询命令生成时，要求返回所有的所有节点和关系，节点可以使用对应的字母进行表示，关系使用对应的r1、r2、r3...进行表示(如果只有一个关系，则仅使用r)。
        2.  **意图对齐检查:**
            *   仔细阅读 **原始用户问题**。
            *   判断初步生成的 Cypher 查询是否真正解决了用户的疑问。例如，用户问的是“桥梁类型”，查询是否错误地匹配了“桥梁构件”？用户问的是“可能发生的疾病”，查询是否匹配了正确的 `may_occur` 关系？
            *   检查 `WHERE` 子句中的实体名称和值是否与用户问题中的关键信息一致（考虑使用 `toLower()` 和 `CONTAINS` 或 `STARTS WITH` 进行鲁棒匹配）。
            *   检查 `RETURN` 子句返回的信息是否是用户可能期望看到的。
            *   生成查询时，关系部分的描述不要使用"<-"、"->"，请使用"-"。
        3.  **修正逻辑:**
            *   如果发现 Schema 不一致（例如，错误的连接模式），**必须** 修正查询以符合 Schema。这可能涉及更改节点标签、关系类型或添加/修改路径中的中间节点（例如，从 `BridgeType` 到 `DiseaseName` 需要经过 `BridgeMember`）。
            *   如果发现查询意图与用户问题不符，**必须** 修正查询逻辑以准确反映用户意图。
            *   如果查询在 Schema 和意图上都正确，则保持原样。
            *   确保查询包含 `LIMIT` 子句（例如 `LIMIT 50`）以防止返回过多结果。
            *   生成查询时，关系部分的描述不要使用"<-"、"->"，请使用"-"。
        4.  **cypher命令要求:**
            *   **最重要:** 查询中的"RETURN"子句应包含能表示查询结果路径或关键信息的节点和关系（例如 `RETURN a, r, b` 或 `RETURN d, r1,bm,r2, bt`）。
            *   比如"MATCH (d:DiseaseName {name: '索鞍锈蚀'})-[:may_occur]-(bm:BridgeMember)-[:has_member]-(bt:BridgeType) RETURN d, bm, bt LIMIT 50"就缺少了中间的关系，所以应该修改为 MATCH (d:DiseaseName {name: '索鞍锈蚀'})-[r1:may_occur]-(bm:BridgeMember)-[r2:has_member]-(bt:BridgeType) RETURN d, r1,bm,r2, bt LIMIT 50       
        5.  **输出要求:**
            *   **绝对只返回最终的、修正后的（或确认无误的）Cypher 查询语句字符串。**
            *   不要包含任何解释、分析过程、代码标记（如 ```cypher ... ```）、确认信息或任何其他文字。

        **示例场景1:**

        *   **原始用户问题:** "索鞍锈蚀这种病害会发生在哪种桥型上"
        *   **知识图谱 Schema:** (如上所述)
        *   **初步生成的 Cypher 查询:** `MATCH (a:DiseaseName {name: '索鞍锈蚀'})-[r:may_occur]-(b:BridgeType) RETURN a, r, b LIMIT 50`
        *   **你的分析:** 按照 Schema 中定义的连接模式，`DiseaseName` 和 `BridgeType` 之间没有关系；用户的问题需要多级查询，需要通过 `BridgeMember` 连接。
        *   **你的修正后输出:**
            ```cypher
            MATCH (d:DiseaseName {name: '索鞍锈蚀'})-[r1:may_occur]-(bm:BridgeMember)-[r2:has_member]-(bt:BridgeType) RETURN d, r1,bm,r2, bt LIMIT 50
            ```

        **现在，请根据接下来提供的【原始用户问题】、【知识图谱 Schema】（始终使用上面定义的）和【初步生成的 Cypher 查询】进行审核和修正，并仅输出最终的 Cypher 查询语句。**
            
        """

        # 构建提示词
        prompt = f"""根据以下意图审核或修改Neo4j Cypher查询语句：
        用户原始问题：{question}

        初步生成的 Cypher 查询：{cypher_init}

        请生成一个精确的Cypher查询语句，确保查询结果包含足够的信息以回答问题。
        """

        try:
            response = await self.generate(prompt, system_prompt, temperature=0.1)
            # 提取Cypher部分
            # cypher = response.strip()      
            cypher = self.extract_cypher_query(response)

            # 确保查询有LIMIT子句
            cypher = cypher.strip()
            if "LIMIT" not in cypher.upper():
                cypher += " LIMIT 50"
            # 删除query中所有的有向箭头如"<"和">"
            cypher = cypher.replace("<", "").replace(">", "")

            return cypher
        except Exception as e:
            logger.error(f"生成Cypher查询失败: {str(e)}")
            # 返回一个基本的查询作为后备策略
            return "MATCH (n) RETURN n LIMIT 10"

    # 在修正后，生成 「路径 + RETURN p + LIMIT」的标准格式。此步骤提供唯一的p变量
    def _unique_alias(self,base="p", used=None):
        import itertools
        """生成一个当前语句未用过的别名"""
        used = used or set()
        if base not in used:
            return base
        for i in itertools.count(1):
            alias = f"{base}{i}"
            if alias not in used:
                return alias

    
    # 在修正后，生成 「路径 + RETURN p + LIMIT」的标准格式。此步骤提供完整的改写函数
    def cypher_revised_p(self, cypher_checked: str) -> str:
        """
        把第二步返回的任意 Cypher 查询改写成
        MATCH pathX = (...)  RETURN pathX LIMIT n
        """
        try:
            text = cypher_checked.strip()
            if re.search(r"\bMATCH\s+\w+\s*=", text, re.I):   # 已经有路径别名
                return re.sub(r"[<>]", "", text).strip()

            # ---- 0. 收集已用别名 ----
            used_alias = set(re.findall(r"\b([A-Za-z]\w*)\s*(?=[,:)\s])", text))
            alias = self._unique_alias("p", used_alias)

            # ---- 1. 定位首个 MATCH（非 OPTIONAL）到其后的 RETURN / WITH / OPTIONAL / UNWIND / CALL / UNION /$ ----
            patt_match = re.compile(r"\bMATCH\b", re.I)
            m = patt_match.search(text)
            if not m:
                raise ValueError("查询里找不到 MATCH")

            start = m.start()

            # 找结束位置：第一个 RETURN 或其他语句关键字
            end_keywords = r"\b(RETURN|WITH|UNWIND|CALL|UNION|OPTIONAL\s+MATCH)\b"
            m_end = re.search(end_keywords, text[m.end():], re.I | re.S)
            end = m_end.start() + m.end() if m_end else len(text)

            match_body = text[m.end():end].strip()

            # ---- 2. 构造新的 MATCH path = (…)
            new_match = f"MATCH {alias} = {match_body}"

            # ---- 3. 处理 RETURN ----
            rest = text[end:].lstrip()
            if rest.upper().startswith("RETURN"):
                # 把 RETURN 替换成 RETURN alias
                rest = re.sub(r"RETURN\s+.*?(?=(\bLIMIT\b|$))",
                            f"RETURN {alias} ",
                            rest,
                            flags=re.I | re.S)
            else:
                rest = f"RETURN {alias} " + rest

            # ---- 4. LIMIT ----
            # 删除所有 LIMIT 再补一个
            rest = re.sub(r"\bLIMIT\s+\d+\s*", "", rest, flags=re.I)
            rest = rest.rstrip()
            if not rest:
                rest = f"LIMIT 50"
            else:
                rest += " LIMIT 50"

            # ---- 5. 组装并清理箭头 ----
            new_cypher = (text[:start] + new_match + " " + rest).replace("<", "").replace(">", "")
            # 去重空白
            new_cypher = re.sub(r"\s+", " ", new_cypher).strip()
            return new_cypher
        except Exception as e:
            logger.error(f"生成Cypher查询失败: {str(e)}")
            return "MATCH (n) RETURN n LIMIT 10"



    def extract_cypher_query(self, response):
        import re
        # 首先使用正则表达式移除<think>...</think>标签及其内部的所有内容
        cleaned_response = re.sub(r'<think>.*?</think>', '', response, flags=re.DOTALL)

        # 寻找包含Cypher查询的部分
        if "```cypher" in cleaned_response:
            cypher_block = "```cypher"
        elif "```" in cleaned_response:
            cypher_block = "```"
        else:
            return cleaned_response  # 如果没有找到合适的标记，返回空字符串

        # 提取位于开始和结束标记之间的Cypher查询语句
        start_index = cleaned_response.find(cypher_block)
        end_index = cleaned_response.find("```", start_index + len(cypher_block))

        if start_index != -1 and end_index != -1:
            cypher = cleaned_response[start_index + len(cypher_block):end_index].strip()
            
            # 根据不同的开头进行相应的替换处理
            if cypher.startswith("cypher"):
                cypher = cypher[6:].strip()  # 去掉"cypher"字样及前后空格
            elif cypher.startswith(cypher_block.strip()):
                cypher = cypher.replace(cypher_block.strip(), "").strip()  # 替换掉开头标记并去除空格
            
            return cypher
        else:
            return ""

    
    async def generate_answer(self, question: str, subgraph: Dict[str, Any], intent: Dict[str, Any]) -> str:
        """
        根据子图数据生成回答
        
        Args:
            question: 用户问题
            subgraph: 子图数据
            intent: 意图解析结果
            
        Returns:
            生成的回答文本
        """
        system_prompt = """
        你是一个基于知识图谱的智能问答助手。根据提供的子图信息，回答用户的问题。
        
        遵循以下规则：
        1. 只使用子图中包含的信息来回答问题，确保回答准确无误
        2. 如果子图信息不足，可以基于常识进行补充，但需明确标注「这是基于我的知识补充的信息，不在知识图谱中」
        3. 回答应简洁、准确、通顺，避免冗余信息
        4. 使用用户提问的语言回答
        5. 如果子图为空，请告知用户未找到相关信息
        6. 回答中应包含关键实体和关系，但不要直接暴露技术细节（如节点ID）
        7. 对于复杂查询，可以分步骤解释，确保用户理解
        8. 如果问题涉及多个实体或关系，确保在回答中涵盖所有相关信息
        """
        
        # 检查子图是否为空
        if not subgraph or (not subgraph.get("nodes") and not subgraph.get("edges")):
            return await self.fallback_answer(question, "未在知识图谱中找到相关信息")
        
        # 创建节点ID到节点数据的映射，方便引用
        node_map = {node['id']: node for node in subgraph.get("nodes", [])}
        
        nodes_text = ""
        for node in subgraph.get("nodes", []):
            # 获取标签：优先使用 "label"，否则尝试 "labels"，最后 fallback 到 "Unknown"
            label = node.get("label")
            if label is None:
                labels = node.get("labels")
                label = labels[0] if isinstance(labels, list) and len(labels) > 0 else "Unknown"

            # 获取属性：优先尝试 attributes，其次 properties，最后空字典
            attributes = node.get("attributes") or node.get("properties") or {}

            # 过滤掉 None 或空字符串的属性值
            filtered_attrs = {k: v for k, v in attributes.items() if v not in (None, "")}

            # 格式化属性信息（用于文本展示）
            attr_text = ", ".join([f"{k}: {v}" for k, v in filtered_attrs.items()])

            # 构建最终的节点信息字符串（供日志或提示词使用）
            nodes_text += f"节点ID: {node['id']}, 标签: {label}, 属性: {{{attr_text}}}\n"

        # 格式化边信息，添加节点标签以增强可读性
        edges_text = ""
        for edge in subgraph.get("edges", []):
            source_node = node_map.get(edge['startNode'], {})
            target_node = node_map.get(edge['endNode'], {})
            source_label = source_node.get('labels', edge['startNode'])
            target_label = target_node.get('labels', edge['endNode'])
            
            attributes = {k: v for k, v in edge.get("attributes", {}).items() if v is not None and v != ""}
            attr_text = ", ".join([f"{k}: {v}" for k, v in attributes.items()])
            
            edges_text += f"关系: {source_label}({edge['startNode']}) --[{edge['type']}]--> {target_label}({edge['endNode']}), 属性: {{{attr_text}}}\n"
        
        # 添加子图摘要信息
        summary = f"子图包含 {len(subgraph.get('nodes', []))} 个节点和 {len(subgraph.get('edges', []))} 个关系。\n\n"
        subgraph_text = f"{summary}节点:\n{nodes_text}\n关系:\n{edges_text}"
        
        # 构建提示词，包含查询类型和意图信息
        query_type = intent.get("query_type", "fact")
        entities = intent.get("entities", [])
        relationships = intent.get("relationships", [])
        
        # 构建更详细的提示词
        prompt = f"""问题: {question}

        查询类型: {query_type}

        子图信息:
        {subgraph_text}

        意图信息: {json.dumps(intent, ensure_ascii=False)}

        请根据以上信息，生成一个准确、简洁、通顺的回答。如果子图信息充足，则严格按照子图信息进行回复， **严禁自行补充相关常识**  。如果子图信息不足，可以基于常识进行补充，但需明确标注。
        """
        
        try:
            return await self.generate(prompt, system_prompt, temperature=0.7)
        except Exception as e:
            logger.error(f"生成回答失败: {str(e)}")
            return await self.fallback_answer(question, str(e))
            
    async def fallback_answer(self, question: str, error_message: str = None, temperature: float = 0.7) -> str:
        """
        当知识图谱查询失败时的备用回答
        
        Args:
            question: 用户问题
            error_message: 错误信息
            temperature: 温度参数，控制回答的创造性
            
        Returns:
            备用回答
        """
        system_prompt = """
        你是一个智能助手。用户的问题无法通过知识图谱回答，请提供以下回复：
        1. 告知用户无法在知识图谱中找到相关信息
        2. 如果可能，提供一个基于你自身知识的简短回答
        3. 建议用户如何重新表述问题以获得更好的结果
        使用用户提问的语言回答。
        """
        
        prompt = f"问题: {question}\n\n错误信息: {error_message if error_message else '未找到相关信息'}"
        
        try:
            return await self.generate(prompt, system_prompt, temperature=temperature)
        except Exception as e:
            logger.error(f"生成备用回答失败: {str(e)}")
            return "抱歉，我无法在知识图谱中找到相关信息，并且生成备用回答时出现错误。请尝试重新表述您的问题。"
    
    async def direct_answer(self, question: str) -> str:
        """
        直接使用大语言模型回答问题（不使用知识图谱）
        
        Args:
            question: 用户问题
            
        Returns:
            模型生成的回答
        """
        system_prompt = """
        你是一个智能助手，请直接回答用户的问题。
        如果你不确定答案，请诚实地表明。
        """
        
        return await self.generate(question, system_prompt, temperature=0.7)